package cn.suimg.neo4j.service.impl;

import cn.suimg.neo4j.annotation.DynamicQuery;
import cn.suimg.neo4j.dto.NeoQueryRequest;
import cn.suimg.neo4j.dto.NeoQueryResult;
import cn.suimg.neo4j.graph.NeoGraph;
import cn.suimg.neo4j.graph.UniversalGraph;
import cn.suimg.neo4j.relationship.NeoRelationship;
import cn.suimg.neo4j.relationship.UniversalRelationship;
import cn.suimg.neo4j.service.Neo4jService;
import cn.suimg.neo4j.util.NeoGraphUtil;
import lombok.extern.slf4j.Slf4j;
import org.neo4j.driver.Value;
import org.neo4j.driver.internal.InternalNode;
import org.neo4j.driver.internal.InternalPath;
import org.neo4j.driver.internal.InternalRelationship;
import org.neo4j.ogm.annotation.NodeEntity;
import org.neo4j.ogm.annotation.RelationshipEntity;
import org.neo4j.ogm.model.Result;
import org.neo4j.ogm.response.model.QueryResultModel;
import org.neo4j.ogm.session.Session;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.function.Function;

import static cn.suimg.neo4j.config.Constant.*;
import static cn.suimg.neo4j.dto.NeoQueryRequest.NameValuePair;

@Slf4j
@Service
public class Neo4jServiceImpl implements Neo4jService {

    @Resource
    private Session session;

    /**
     * 查询图谱的名称
     */
    private static final Function<Class<? extends NeoGraph>,String> queryGraphLabel = neoGraph -> neoGraph.getAnnotation(NodeEntity.class).label();

    /**
     * 查询关系的类型
     */
    private static final Function<Class<? extends NeoRelationship>,String> queryRelationshipType = neoRelationship -> neoRelationship.getAnnotation(RelationshipEntity.class).type();

    @Override
    public NeoQueryResult dynamicQuery(NeoQueryRequest queryRequest) {
        //1.从内存读取 请求源对应的Class类
        Class<? extends NeoGraph> graphClass = DYNAMIC_QUERY_CLASS_MAPPING.get(queryRequest.getSource());
        if(graphClass == null)
            return new NeoQueryResult();

        String sourceLabel = queryGraphLabel.apply(graphClass);

        //2.读取DynamicQuery注解配置
        DynamicQuery annotation = graphClass.getAnnotation(DynamicQuery.class);
        Class<? extends NeoGraph>[] graphs = annotation.graph();
        Class<? extends NeoRelationship>[] relationships = annotation.relationship();
        if(graphs.length != relationships.length)
            throw new RuntimeException("@DynamicQuery annotation config error,graph array and annotation array's length should be same");

        //3.最终得到查询CQL和计数CQL
        String queryCql="",countCql="";
        int skip = (queryRequest.getPageNo() - 1) * queryRequest.getPageSize();
        if(!"*".equals(queryRequest.getTarget())){
            //第二步读取注解的值进行map映射,key是节点类,value是关系类
            Map<Class<? extends NeoGraph>,Class<? extends NeoRelationship>> dynamicQueryMapping = new HashMap<>();
            for(int i = 0;i < graphs.length;i++){
                dynamicQueryMapping.put(graphs[i],relationships[i]);
            }
            String targetName = "",typeName = "",type = "",targetLabel = "";
            //遍历刚才的map,使用lambda遍历还需要注意变量final问题 这里使用entrySet遍历比较方便
            Set<Map.Entry<Class<? extends NeoGraph>, Class<? extends NeoRelationship>>> entries = dynamicQueryMapping.entrySet();
            for (Map.Entry<Class<? extends NeoGraph>, Class<? extends NeoRelationship>> entry : entries) {
                Class<? extends NeoGraph> graph = entry.getKey();
                Class<? extends NeoRelationship> relationship = entry.getValue();
                targetName = getClassName.apply(graph);
                if(targetName.toLowerCase().equalsIgnoreCase(queryRequest.getTarget().toLowerCase())){
                    typeName = getClassName.apply(relationship);
                    type = queryRelationshipType.apply(relationship);
                    targetLabel = queryGraphLabel.apply(graph);
                    break;
                }
            }
            //精准查询某个关系类
            queryCql = String.format(CQL_QUERY_TEMPLATE, queryRequest.getSource(), sourceLabel, typeName, type, queryRequest.getTarget(), targetLabel, buildWhereCondition(queryRequest.getFitter()), skip, queryRequest.getPageSize());
            countCql = String.format(CQL_COUNT_TEMPLATE, queryRequest.getSource(), sourceLabel, typeName, type, queryRequest.getTarget(), targetLabel, buildWhereCondition(queryRequest.getFitter()));
        }else {
            //查询节点所有的关系
            queryCql = String.format(CQL_QUERY_ANYTHING_TEMPLATE,queryRequest.getSource(),sourceLabel,buildWhereCondition(queryRequest.getFitter()),skip,queryRequest.getPageSize());
            countCql = String.format(CQL_COUNT_ANYTHING_TEMPLATE,queryRequest.getSource(),sourceLabel,buildWhereCondition(queryRequest.getFitter()));
        }

        log.debug("query CQL:{}",queryCql);
        log.debug("count CQL:{}",countCql);

        List<NeoGraph> graphList = new ArrayList<>();
        List<NeoRelationship> relationshipList = new ArrayList<>();
        Result result = session.query(queryCql, new HashMap<>());
        QueryResultModel countQueryResultModel = (QueryResultModel) result;

        countQueryResultModel.queryResults().forEach(map -> map.forEach((key,value) -> {
            if(value instanceof NeoGraph)
                graphList.add((NeoGraph) value);
            else if (value instanceof NeoRelationship)
                relationshipList.add((NeoRelationship) value);
            else if ("r".equals(key)){
                InternalPath.SelfContainedSegment obj = ((InternalPath.SelfContainedSegment[]) value)[0];
                InternalNode start = (InternalNode)obj.start();
                InternalNode end = (InternalNode) obj.end();
                InternalRelationship relationship = (InternalRelationship)obj.relationship();

                UniversalGraph startGraph = new UniversalGraph() {{
                    String label = start.labels().iterator().next();
                    setId(start.id());
                    setLabel(label);
                    setGraph(LABEL_2_GRAPH_MAPPING.get(label));
                    setProperties(start.asMap(Value::asObject));
                }};
                UniversalGraph endGraph = new UniversalGraph() {{
                    String label = end.labels().iterator().next();
                    setId(end.id());
                    setLabel(label);
                    setGraph(LABEL_2_GRAPH_MAPPING.get(label));
                    setProperties(end.asMap(Value::asObject));
                }};
                graphList.add(startGraph);
                graphList.add(endGraph);
                relationshipList.add(new UniversalRelationship(){{

                    setId(relationship.id());
                    setLabel(relationship.type());
                    setStartNode(startGraph);
                    setEndNode(endGraph);
                    setProperties(relationship.asMap(Value::asObject));
                }});

            }
        }));

        Long total = session.queryForObject(Long.class, countCql, new HashMap<>());

        return NeoGraphUtil.buildWebResult(graphList, relationshipList,total);
    }

    private String buildWhereCondition(List<NameValuePair> nameValuePairList){
        StringBuilder cqlWhereBuilder = new StringBuilder();
        nameValuePairList.forEach(nameValuePair -> {
            cqlWhereBuilder.append(nameValuePair.getName());
            String operate = CQL_OPERATE_MAPPING.get(nameValuePair.getOperate());
            cqlWhereBuilder.append(" ").append(operate).append(" ");
            Object object = nameValuePair.getValue();
            if(object instanceof String){
                String value = ((String) object).replaceAll("\"", "")
                        .replaceAll("'", "");
                cqlWhereBuilder.append("'").append(value).append("'");
            }else if (object instanceof Integer||object instanceof Long||object instanceof Double){
                cqlWhereBuilder.append(object);
            }
            cqlWhereBuilder.append(" ");
            cqlWhereBuilder.append("and ");
        });
        return cqlWhereBuilder.length() == 0 ? "1 = 1" : cqlWhereBuilder.substring(0,cqlWhereBuilder.length() - 4);

    }

}
